Smoothing trajectories¶

Binder IPYNB HTML

To smooth trajectories, we can use a Kalman filter. The implemented KalmanSmootherCV is based on the assumption of a nearly-constant velocity (CV) model. To use KalmanSmootherCV, the optional dependency StoneSoup needs to be installed.

Documentation

A closely related type of operation is trajectory generalization which is coverd in a separate notebook.

In [1]:
import pandas as pd
import geopandas as gpd
import movingpandas as mpd
import shapely as shp
import hvplot.pandas 
import matplotlib.pyplot as plt

from geopandas import GeoDataFrame, read_file
from shapely.geometry import Point, LineString, Polygon
from datetime import datetime, timedelta
from holoviews import opts, dim

import warnings
warnings.filterwarnings('ignore')

plot_defaults = {'linewidth':5, 'capstyle':'round', 'figsize':(9,3), 'legend':True}
opts.defaults(opts.Overlay(active_tools=['wheel_zoom']))
hvplot_defaults = {'tiles':'CartoLight', 'frame_height':320, 'frame_width':320, 'cmap':'Viridis', 'colorbar':True}

mpd.show_versions()
MovingPandas 0.15.rc1

SYSTEM INFO
-----------
python     : 3.9.15 | packaged by conda-forge | (main, Nov 22 2022, 08:39:05) [MSC v.1929 64 bit (AMD64)]
executable : H:\miniconda3\envs\mpd-ex\python.exe
machine    : Windows-10-10.0.19045-SP0

GEOS, GDAL, PROJ INFO
---------------------
GEOS       : None
GEOS lib   : None
GDAL       : 3.5.0
GDAL data dir: None
PROJ       : 9.0.0
PROJ data dir: H:\miniconda3\pkgs\proj-9.0.0-h1cfcee9_1\Library\share\proj

PYTHON DEPENDENCIES
-------------------
geopandas  : 0.12.2
pandas     : 1.5.3
fiona      : 1.8.21
numpy      : 1.24.1
shapely    : 1.8.2
rtree      : 1.0.0
pyproj     : 3.3.1
matplotlib : 3.6.3
mapclassify: None
geopy      : 2.3.0
holoviews  : 1.14.9
hvplot     : 0.8.2
geoviews   : 1.9.6
stonesoup  : 0.1b11
In [2]:
gdf = read_file('../data/geolife_small.gpkg')
traj_collection = mpd.TrajectoryCollection(gdf, 'trajectory_id', t='t')
In [3]:
split = mpd.ObservationGapSplitter(traj_collection).split(gap=timedelta(minutes=15))
In [4]:
split.plot(column='trajectory_id', **plot_defaults)
Out[4]:
<AxesSubplot: >

KalmanSmootherCV¶

This smoother operates on the assumption of a nearly-constant velocity (CV) model. The process_noise_std and measurement_noise_std parameters can be used to tune the smoother:

  • process_noise_std governs the uncertainty associated with the adherence of the new (smooth) trajectories to the CV model assumption; higher values relax the assumption, therefore leading to less-smooth trajectories, and vice-versa.
  • measurement_noise_std controls the assumed error in the original trajectories; higher values dictate that the original trajectories are expected to be noisier (and therefore, less reliable), thus leading to smoother trajectories, and vice-versa.

Try tuning these parameters and observe the resulting trajectories:

In [5]:
smooth = mpd.KalmanSmootherCV(split).smooth(process_noise_std=0.1, measurement_noise_std=10)
print(smooth)
TrajectoryCollection with 11 trajectories
In [6]:
kwargs = {**hvplot_defaults, 'line_width':4}
(split.hvplot(title='Original Trajectories', **kwargs) + 
 smooth.hvplot(title='Smooth Trajectories', **kwargs))
Out[6]:
In [7]:
kwargs = {**hvplot_defaults, 'c':'speed', 'line_width':7, 'clim':(0,20)}
(split.trajectories[2].hvplot(title='Original Trajectory', **kwargs) + 
 smooth.trajectories[2].hvplot(title='Smooth Trajectory', **kwargs))
Out[7]:

OutlierCleaner¶

In [8]:
traj = split.trajectories[2]

cleaned = traj.copy()
cleaned.add_speed(overwrite=True)
for i in range(0,10):
    cleaned = mpd.OutlierCleaner(cleaned).clean({'speed': 1})

smoothed = mpd.KalmanSmootherCV(cleaned).smooth(process_noise_std=0.1, measurement_noise_std=10)
    
(traj.hvplot(title='Original Trajectory', **kwargs) + 
 cleaned.hvplot(title='Cleaned Trajectory', **kwargs) + 
 smoothed.hvplot(title='Cleaned & Smoothed Trajectory', **kwargs))
Out[8]:
In [ ]: